namespace SimpleWebServices.Security.Cryptography
{
    using System;
    using System.IO;
    using System.Net.Sockets;
    using System.Runtime.Serialization;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.Text;
    using Xunit;

    public sealed class MD5HashFacts
    {
        private const string EmptyHash = "1B2M2Y8AsgTpgAmY7PhCfg==";
        private const string JigsawHash = "0TMnkhCZtrIjdTtJk6x3+Q==";

        /// http://jigsaw.w3.org/HTTP/h-content-md5.html
        private const string JigsawHtml = "<HTML>\n"
            + "<HEAD>\n  <!-- Created with 'cat' and 'vi'  -->\n"
            + "<TITLE>Retry-After header</TITLE>\n"
            + "</HEAD>\n"
            + "<BODY>\n"
            + "<P>\n"
            + "<A HREF=\"..\"><IMG SRC=\"/icons/jigsaw\" ALT=\"Jigsaw\" BORDER=\"0\" WIDTH=\"212\"\n    HEIGHT=\"49\"></A>\n"
            + "<H1>\nThe <I>Content-MD5</I> header\n</H1>\n"
            + "<P>This pages is served along with its MD5 digest, you take\na look at the headers, as it is quite difficult to do an auto-referent\npage about its md5 signature :)\n"
            + "</P>\n  <HR>\n<BR>\n"
            + "<A HREF=\"mailto:jigsaw@w3.org\">jigsaw@w3.org</A>\n"
            + "</BODY></HTML>\n \n";

        [Fact]
        public void typedef()
        {
            Assert.True(new ObjectExpectations<MD5Hash>()
                .DerivesFrom<object>()
                .IsConcreteClass()
                .IsSealed()
                .HasDefaultConstructor()
                .Serializable()
                .Implements<IComparable>()
                .Result);
        }

        [Fact]
        public void ctor()
        {
            Assert.NotNull(new MD5Hash());
        }

        [Fact]
        public void ctor_string()
        {
            Assert.NotNull(new MD5Hash("test"));
        }

        [Fact]
        public void ctor_stringEmpty()
        {
            Assert.NotNull(new MD5Hash(string.Empty));
        }

        [Fact]
        public void ctor_stringNull()
        {
            Assert.NotNull(new MD5Hash(null));
        }

        [Fact]
        public void ctor_SerializationInfo_StreamingContext()
        {
            MD5Hash expected = new MD5Hash("test");
            MD5Hash actual = null;

            using (Stream stream = new MemoryStream())
            {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(stream, new MD5Hash("test"));
                stream.Position = 0;
                actual = (MD5Hash)formatter.Deserialize(stream);
            }

            Assert.Equal<MD5Hash>(expected, actual);
        }

        [Fact]
        public void opImplicit_stringNull_MD5Hash()
        {
            string expected = null;
            string actual = null as MD5Hash;

            Assert.Equal<string>(expected, actual);
        }

        [Fact]
        public void opImplicit_stringEmpty_MD5Hash()
        {
            string expected = string.Empty;
            string actual = new MD5Hash(expected);

            Assert.Equal<string>(expected, actual);
        }

        [Fact]
        public void opImplicit_string_MD5Hash()
        {
            string expected = "test";
            string actual = new MD5Hash(expected);

            Assert.Equal<string>(expected, actual);
        }

        [Fact]
        public void opImplicit_MD5Hash_stringNull()
        {
            MD5Hash obj = null as string;

            Assert.Null(obj);
        }

        [Fact]
        public void opImplicit_MD5Hash_stringEmpty()
        {
            MD5Hash expected = string.Empty;
            MD5Hash actual = new MD5Hash(string.Empty);

            Assert.Equal<MD5Hash>(expected, actual);
        }

        [Fact]
        public void opImplicit_MD5Hash_string()
        {
            MD5Hash expected = "test";
            MD5Hash actual = new MD5Hash("test");

            Assert.Equal<MD5Hash>(expected, actual);
        }

        [Fact]
        public void opEquality_MD5Hash_MD5Hash_True()
        {
            MD5Hash operand1 = new MD5Hash();
            MD5Hash operand2 = new MD5Hash();

            Assert.True(operand1 == operand2);
        }

        [Fact]
        public void opEquality_MD5Hash_MD5Hash_False()
        {
            MD5Hash operand1 = new MD5Hash("foo");
            MD5Hash operand2 = new MD5Hash("bar");

            Assert.False(operand1 == operand2);
        }

        [Fact]
        public void opEquality_MD5Hash_MD5Hash_Same()
        {
            MD5Hash operand1 = new MD5Hash();
            MD5Hash operand2 = operand1;

            Assert.True(operand1 == operand2);
        }

        [Fact]
        public void opEquality_MD5HashNull_MD5Hash()
        {
            MD5Hash operand1 = null;
            MD5Hash operand2 = new MD5Hash();

            Assert.False(operand1 == operand2);
        }

        [Fact]
        public void opEquality_MD5Hash_MD5HashNull()
        {
            MD5Hash operand1 = new MD5Hash();
            MD5Hash operand2 = null;

            Assert.False(operand1 == operand2);
        }

        [Fact]
        public void opInequality_MD5Hash_MD5Hash_True()
        {
            MD5Hash operand1 = new MD5Hash("foo");
            MD5Hash operand2 = new MD5Hash("bar");

            Assert.True(operand1 != operand2);
        }

        [Fact]
        public void opInequality_MD5Hash_MD5Hash_False()
        {
            MD5Hash operand1 = new MD5Hash();
            MD5Hash operand2 = new MD5Hash();

            Assert.False(operand1 != operand2);
        }

        [Fact]
        public void opInequality_MD5Hash_MD5Hash_Same()
        {
            MD5Hash operand1 = new MD5Hash();
            MD5Hash operand2 = operand1;

            Assert.False(operand1 != operand2);
        }

        [Fact]
        public void opInequality_MD5HashNull_MD5Hash()
        {
            MD5Hash operand1 = null;
            MD5Hash operand2 = new MD5Hash();

            Assert.True(operand1 != operand2);
        }

        [Fact]
        public void opInequality_MD5Hash_MD5HashNull()
        {
            MD5Hash operand1 = new MD5Hash();
            MD5Hash operand2 = null;

            Assert.True(operand1 != operand2);
        }

        [Fact]
        public void opLesser_MD5Hash_MD5Hash_Same()
        {
            MD5Hash operand1 = new MD5Hash();
            MD5Hash operand2 = operand1;

            Assert.False(operand1 < operand2);
        }

        [Fact]
        public void opLesser_MD5Hash_MD5Hash_True()
        {
            MD5Hash operand1 = new MD5Hash("bar");
            MD5Hash operand2 = new MD5Hash("foo");

            Assert.True(operand1 < operand2);
        }

        [Fact]
        public void opLesser_MD5Hash_MD5Hash_False()
        {
            MD5Hash operand1 = new MD5Hash("foo");
            MD5Hash operand2 = new MD5Hash("bar");

            Assert.False(operand1 < operand2);
        }

        [Fact]
        public void opLesser_MD5HashNull_MD5Hash()
        {
            MD5Hash operand1 = null;
            MD5Hash operand2 = new MD5Hash();

            Assert.True(operand1 < operand2);
        }

        [Fact]
        public void opLesser_MD5Hash_MD5HashNull()
        {
            MD5Hash operand1 = new MD5Hash();
            MD5Hash operand2 = null;

            Assert.False(operand1 < operand2);
        }

        [Fact]
        public void opGreater_MD5Hash_MD5Hash_Same()
        {
            MD5Hash operand1 = new MD5Hash();
            MD5Hash operand2 = operand1;

            Assert.False(operand1 > operand2);
        }

        [Fact]
        public void opGreater_MD5Hash_MD5Hash_True()
        {
            MD5Hash operand1 = new MD5Hash("foo");
            MD5Hash operand2 = new MD5Hash("bar");

            Assert.True(operand1 > operand2);
        }

        [Fact]
        public void opGreater_MD5Hash_MD5Hash_False()
        {
            MD5Hash operand1 = new MD5Hash("bar");
            MD5Hash operand2 = new MD5Hash("foo");

            Assert.False(operand1 > operand2);
        }

        [Fact]
        public void opGreater_MD5HashNull_MD5Hash()
        {
            MD5Hash operand1 = null;
            MD5Hash operand2 = new MD5Hash();

            Assert.False(operand1 > operand2);
        }

        [Fact]
        public void opGreater_MD5Hash_MD5HashNull()
        {
            MD5Hash operand1 = new MD5Hash();
            MD5Hash operand2 = null;

            Assert.True(operand1 > operand2);
        }

        [Fact]
        public void op_Compare_MD5Hash_MD5Hash_Equal()
        {
            MD5Hash comparand1 = new MD5Hash();
            MD5Hash comparand2 = new MD5Hash();

            Assert.Equal<int>(0, MD5Hash.Compare(comparand1, comparand2));
        }

        [Fact]
        public void op_Compare_MD5Hash_MD5Hash_Same()
        {
            MD5Hash comparand1 = new MD5Hash();
            MD5Hash comparand2 = comparand1;

            Assert.Equal<int>(0, MD5Hash.Compare(comparand1, comparand2));
        }

        [Fact]
        public void op_Compare_MD5HashNull_MD5Hash()
        {
            MD5Hash comparand1 = null;
            MD5Hash comparand2 = new MD5Hash();

            Assert.True(MD5Hash.Compare(comparand1, comparand2) < 0);
        }

        [Fact]
        public void op_Compare_MD5Hash_MD5HashNull()
        {
            MD5Hash comparand1 = new MD5Hash();
            MD5Hash comparand2 = null;

            Assert.True(MD5Hash.Compare(comparand1, comparand2) > 0);
        }

        [Fact]
        public void op_Compare_MD5HashGreater_MD5Hash()
        {
            MD5Hash comparand1 = new MD5Hash("foo");
            MD5Hash comparand2 = new MD5Hash("bar");

            Assert.True(MD5Hash.Compare(comparand1, comparand2) > 0);
        }

        [Fact]
        public void op_Compare_MD5HashLesser_MD5Hash()
        {
            MD5Hash comparand1 = new MD5Hash("bar");
            MD5Hash comparand2 = new MD5Hash("foo");

            Assert.True(MD5Hash.Compare(comparand1, comparand2) < 0);
        }

        [Fact]
        public void op_Compute_stringNull()
        {
            Assert.Throws<ArgumentNullException>(() => MD5Hash.Compute(null as string));
        }

        [Fact]
        public void op_Compute_stringEmpty()
        {
            MD5Hash expected = EmptyHash;
            MD5Hash actual = MD5Hash.Compute(string.Empty);

            Assert.Equal<MD5Hash>(expected, actual);
        }

        [Fact]
        public void op_Compute_string()
        {
            MD5Hash expected = JigsawHash;
            MD5Hash actual = MD5Hash.Compute(JigsawHtml);

            Assert.Equal<MD5Hash>(expected, actual);
        }

        [Fact]
        public void op_Compute_stringNull_Encoding()
        {
            Assert.Throws<ArgumentNullException>(() => MD5Hash.Compute(null, UTF8Encoding.Default));
        }

        [Fact]
        public void op_Compute_string_EncodingNull()
        {
            Assert.Throws<ArgumentNullException>(() => MD5Hash.Compute("test", null));
        }

        [Fact]
        public void op_Compute_stringEmpty_Encoding()
        {
            MD5Hash expected = EmptyHash;
            MD5Hash actual = MD5Hash.Compute(string.Empty, ASCIIEncoding.Default);

            Assert.Equal<MD5Hash>(expected, actual);
        }

        [Fact]
        public void op_Compute_string_Encoding()
        {
            MD5Hash expected = JigsawHash;
            MD5Hash actual = MD5Hash.Compute(JigsawHtml, ASCIIEncoding.Default);

            Assert.Equal<MD5Hash>(expected, actual);
        }

        [Fact]
        public void op_Compute_FileSystemInfoNull()
        {
            Assert.Throws<ArgumentNullException>(() => MD5Hash.Compute(null as FileSystemInfo));
        }

        [Fact]
        public void op_Compute_FileSystemInfoEmpty()
        {
            MD5Hash expected = EmptyHash;
            MD5Hash actual = MD5Hash.Compute(new FileInfo(@"Security\Cryptography\empty.html"));

            Assert.Equal<MD5Hash>(expected, actual);
        }

        [Fact]
        public void op_Compute_FileSystemInfo()
        {
            MD5Hash expected = JigsawHash;
            MD5Hash actual = MD5Hash.Compute(new FileInfo(@"Security\Cryptography\jigsaw.html"));

            Assert.Equal<MD5Hash>(expected, actual);
        }

        [Fact]
        public void op_Compute_UriNull()
        {
            Assert.Throws<ArgumentNullException>(() => MD5Hash.Compute(null as Uri));
        }

        [Fact]
        public void op_Compute_Uri()
        {
            try
            {
                MD5Hash expected = JigsawHash;
                MD5Hash actual = MD5Hash.Compute(new Uri("http://jigsaw.w3.org/HTTP/h-content-md5.html"));

                Assert.Equal<MD5Hash>(expected, actual);
            }
            catch (SocketException)
            {
            }
        }

        [Fact]
        public void op_Compute_streamNull()
        {
            Assert.Throws<ArgumentNullException>(() => MD5Hash.Compute(null as Stream));
        }

        [Fact]
        public void op_Compute_streamEmpty()
        {
            MD5Hash expected = EmptyHash;
            MD5Hash actual = null;

            using (MemoryStream stream = new MemoryStream())
            {
                actual = MD5Hash.Compute(stream);
            }

            Assert.Equal<MD5Hash>(expected, actual);
        }

        [Fact]
        public void op_Compute_stream()
        {
            MD5Hash expected = JigsawHash;
            MD5Hash actual = null;

            using (MemoryStream stream = new MemoryStream())
            {
                using (StreamWriter writer = new StreamWriter(stream))
                {
                    writer.Write(JigsawHtml);
                    writer.Flush();
                    stream.Position = 0;

                    actual = MD5Hash.Compute(stream);
                }
            }

            Assert.Equal<MD5Hash>(expected, actual);
        }

        [Fact]
        public void op_Compute_byteNull()
        {
            Assert.Throws<ArgumentNullException>(() => MD5Hash.Compute(null as byte[]));
        }

        [Fact]
        public void op_Compute_byteEmpty()
        {
            MD5Hash expected = EmptyHash;
            MD5Hash actual = MD5Hash.Compute(new byte[] { });

            Assert.Equal<MD5Hash>(expected, actual);
        }

        [Fact]
        public void op_Compute_byte()
        {
            MD5Hash expected = JigsawHash;
            MD5Hash actual = MD5Hash.Compute(ASCIIEncoding.Default.GetBytes(JigsawHtml));

            Assert.Equal<MD5Hash>(expected, actual);
        }

        [Fact]
        public void op_Equals_object()
        {
            MD5Hash obj = new MD5Hash();

            Assert.True(new MD5Hash().Equals(obj));
        }

        [Fact]
        public void op_Equals_object_this()
        {
            MD5Hash obj = new MD5Hash();

            Assert.True(obj.Equals(obj));
        }

        [Fact]
        public void op_Equals_objectNull()
        {
            Assert.False(new MD5Hash().Equals(null));
        }

        [Fact]
        public void op_Equals_objectString()
        {
            Assert.False(new MD5Hash("test").Equals("test"));
        }

        [Fact]
        public void op_GetHashCode()
        {
            MD5Hash obj = new MD5Hash("test");

            int expected = obj.ToString().GetHashCode();
            int actual = obj.GetHashCode();

            Assert.Equal<int>(expected, actual);
        }

        [Fact]
        public void op_ToString()
        {
            string expected = "test";
            string actual = new MD5Hash(expected).ToString();

            Assert.Equal<string>(expected, actual);
        }

        [Fact]
        public void op_CompareTo_objectNull()
        {
            Assert.True(new MD5Hash().CompareTo(null) > 0);
        }

        [Fact]
        public void op_CompareTo_objectSame()
        {
            MD5Hash obj = new MD5Hash("test");

            Assert.Equal<int>(0, obj.CompareTo(obj));
        }

        [Fact]
        public void op_CompareTo_object()
        {
            MD5Hash left = new MD5Hash("test");
            MD5Hash right = new MD5Hash("test");

            Assert.Equal<int>(0, left.CompareTo(right));
        }

        [Fact]
        public void op_CompareTo_objectLesser()
        {
            MD5Hash left = new MD5Hash("bar");
            MD5Hash right = new MD5Hash("foo");

            Assert.True(left.CompareTo(right) < 0);
        }

        [Fact]
        public void op_CompareTo_objectGreater()
        {
            MD5Hash left = new MD5Hash("foo");
            MD5Hash right = new MD5Hash("bar");

            Assert.True(left.CompareTo(right) > 0);
        }

        [Fact]
        public void op_CompareTo_objectInt32()
        {
            Assert.Throws<ArgumentException>(() => new MD5Hash().CompareTo(123));
        }

        [Fact]
        public void op_GetObjectData_SerializationInfoNull_StreamingContext()
        {
            StreamingContext context = new StreamingContext(StreamingContextStates.All);

            Assert.Throws<ArgumentNullException>(() => (new MD5Hash("test") as ISerializable).GetObjectData(null as SerializationInfo, context));
        }

        [Fact]
        public void op_GetObjectData_SerializationInfo_StreamingContext()
        {
            SerializationInfo info = new SerializationInfo(typeof(MD5Hash), new FormatterConverter());
            StreamingContext context = new StreamingContext(StreamingContextStates.All);

            string expected = "test";

            (new MD5Hash(expected) as ISerializable).GetObjectData(info, context);

            string actual = info.GetString("_value");

            Assert.Equal<string>(expected, actual);
        }
    }
}